/** @file log.cpp
	@brief Logging system */

#include "log.h"

using namespace G3;

// Default logfile sizelimit
long G3::Default_Log_Sizelimit = 102400;
// Default logfile
G3::LOG G3::MainLog ("engine.log");

//==============================================================================
// Constructors and a destructor
//==============================================================================
G3::LOG::LOG (long logSizeLimit) {
    WriteHeaderOnOpen = true;
    WriteDateOnReport = false;

    LogSizeLimit = logSizeLimit;

    File = NULL;
}

G3::LOG::LOG (const char *filename, long logSizeLimit) {
    WriteHeaderOnOpen = true;
    WriteDateOnReport = false;

    LogSizeLimit = logSizeLimit;

    File = NULL;
    Open (filename);
}

G3::LOG::~LOG () {
    Close ();
}

//==============================================================================
// Opens a logfile for appending if the file is not larger than 100KB
//==============================================================================
bool G3::LOG::Open (const char *fname) {
     struct stat st;

     if (fname == NULL)
        return false;

     // Is the logfile too large already?
     // Or are we even supposed to check that?
     if ((LogSizeLimit > -1) && !stat (fname, &st))
        if (st.st_size >= LogSizeLimit)
           remove (fname);

     // Append the logfile
     File = fopen (fname, "a");

     if (!IsOk ())
        return false;

     // Write log header if asked
     if (WriteHeaderOnOpen)
        WriteHeader ();

     return true;
}

//==============================================================================
// Writes a header (version, log update time) into a logfile
//==============================================================================
void G3::LOG::WriteHeader () {
     // Get local time
     time_t date = time (NULL);
     struct tm *lt = localtime (&date);

     // Write header
     fprintf (File, "=============================================\n"
                    "Game3ngine %s log launched: %s\n\n", G3_VERSION, asctime (lt));

     // And flush it into the file
     fflush (File);
}

//==============================================================================
// Writes the date (log update time) into a logfile
//==============================================================================
void G3::LOG::WriteDate () {
     // Get local time
     time_t date = time (NULL);
     struct tm *lt = localtime (&date);

     // Write it down
     fprintf (File, "%s: ", asctime (lt));

     // And flush it into the file
     fflush (File);
}

//==============================================================================
// Reports something to the log
//==============================================================================
void G3::LOG::Report (const char *fmt, ...){
	va_list    ap;
	char       *buf = NULL;
	char       *newbuf = NULL;
	int			buffered = 0;
	int			size = LOG_BUFFER;

	// Nothing to be written?
	if (fmt == NULL)
		return;

	// Is the log okay?
	if (!IsOk ())
		return;

	buf = (char *) malloc (size * sizeof (char));

	if (!buf)
        return;

	// Basically a safe vsprintf
	while (1) {
		/* Try to print in the allocated space. */
		va_start (ap, fmt);

		buffered = vsnprintf (buf, size, fmt, ap);

		va_end (ap);

		/* If that worked, return the string. */
		if (buffered > -1 && buffered < size)
			break;
		/* Else try again with more space. */
		if (buffered > -1)    /* glibc 2.1 */
			size = buffered + 1; /* precisely what is needed */
		else           /* glibc 2.0 */
			size *= 2;  /* twice the old size */
		if ((newbuf = (char *) realloc (buf, size)) == NULL) {
			free (buf);
			return;
		} else {
			buf = newbuf;
		}
	}

	// Write date if asked
	if (WriteDateOnReport)
		WriteDate ();

	// Write the report
	fprintf (File, "%s", buf);

	// Free the buffer
	free (buf);

    // And flush changes
    fflush (File);
}

//==============================================================================
// Closes the logfile
//==============================================================================
void G3::LOG::Close () {
     if (File)
        fclose (File);

     File = NULL;
}

//==============================================================================
// Is there a logfile open? Is there any memory allocated for this class?
//==============================================================================
bool G3::LOG::IsOk () {
    if (!this || !File)
        return false;

    return true;
}

//==============================================================================
// An assert function
//
//	TODO:: Bring up a dialog box with options: Break, Ignore, Ignore Always
//==============================================================================
bool G3::AssertFunc (bool aExpression, const char *aDescription, int aLine, const char *aFile, bool *aIgnoreAlways) {
	if (!aExpression) {
		printf ("Assertion failed: ");
		MainLog.Report ("Assertion failed: ");

		if (aDescription) {
			printf ("%s\n", aDescription);
			MainLog.Report ("%s\n", aDescription);
		} else {
			printf ("Unknown\n");
			MainLog.Report ("Unknown\n");
		}

		if (aFile) {
			printf ("\tFile: %s\n", aFile);
			MainLog.Report ("\tFile: %s\n", aFile);
		} else {
			printf ("\tFile: Unknown\n");
			MainLog.Report ("\tFile: Unknown\n");
		}

		printf ("\tLine: %d\n", aLine);
		MainLog.Report ("\tLine: %d\n", aLine);

		// Now it only breaks
		return true;
	}
	return false;
}

//==============================================================================
// Performs an OpenGL error check
//==============================================================================
bool G3::glErrCheck (LOG *log, const std::string &glfunc, const char *file, const char *func, int line) {
    std::string str;
    int error = glGetError ();
    if (error != GL_NO_ERROR) {
        if (log) {
            str = std::string (file) + ":" + std::string (func) + ":" + IntegerToStr (line) + " (" + glfunc + "): ";
            log->Report ("%sOpenGL threw error %d (%s)..\n", str.c_str (), error, gluErrorString (error));
        }
        return true;
    }
    return false;
}
